﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Text;
using System.Windows.Forms;
using Alerter.Controls;
using Alerter.DataSet;
using Communication;
using E4RPTools;
using PDUConverter;

namespace Alerter
{
    public class Controller
    {
        private const string ProductName = "Alerter";
        private SqlConnection _connection;
        private GsmCommMain _communication;

        public IWin32Window OwnerWindow { get; set; }
        public int ComPort { get; set; }
        public int BaudRate { get; set; }
        public int Timeout { get; set; }
        public bool PhoneIsConnected { get; set; }
        public int Version { get; set; }
        public string LogFile { get; set; }

        #region Private Methods

        private void FixConnection()
        {
            if (_connection.State == ConnectionState.Open)
                _connection.Close();
            _connection.Open();
        }

        private bool IsPhoneConnected(bool showException)
        {
            var comm = new GsmCommMain(ComPort, BaudRate, Timeout);
            try
            {
                comm.Open();
                var connected = comm.IsConnected();
                comm.Close();
                if(!connected)
                    return false;
            }
            catch (Exception ex)
            {
                if (showException)
                    MessageBox.Show(OwnerWindow, "Erro na conexão: " + ex.Message, "Instalação conexão", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return false;
            }
            return true;
        }

        public bool ShowPhoneConnection()
        {
            var communicationSettings = new CommunicationSettingsXtraForm
            {
                COMPort = ComPort,
                BaudRate = BaudRate,
                Timeout = Timeout
            };
            if (communicationSettings.ShowDialog() != DialogResult.OK)
                return false;
            ComPort = communicationSettings.COMPort;
            BaudRate = communicationSettings.BaudRate;
            Timeout = communicationSettings.Timeout;
            return IsPhoneConnected(true);
        }

        private void OnPhoneSendingMessage(string message)
        {
            EventHandler<SendingSmsArgs> eventHandler = PhoneSendingMessage;
            if (eventHandler != null)
                eventHandler(this, new SendingSmsArgs(message));
        }

        private void OnPhoneConnectionChange()
        {
            EventHandler<EventArgs> eventHandler = PhoneConnectionChanged;
            if (eventHandler != null)
                eventHandler(this, new EventArgs());
        }

        public event EventHandler<EventArgs> PhoneConnectionChanged;
        public event EventHandler<SendingSmsArgs> PhoneSendingMessage; 

        void CommunicationPhoneDisconnected(object sender, EventArgs e)
        {
            PhoneIsConnected = false;
            OnPhoneConnectionChange();
        }

        void CommunicationPhoneConnected(object sender, EventArgs e)
        {
            PhoneIsConnected = true;
            OnPhoneConnectionChange();
        }

        //private void SendContactSms(bool unicode, GrupoDataSet groups, ContactoDataSet contacts, MensagemDataSet.ContactoRow contact, SqlTransaction sqlTransaction)
        //{
        //    if (contact.Estado == MessageState.Enviada.ToString())
        //        return;            
        //    try
        //    {
        //        string state, error;
        //        try
        //        {
        //            var destinationPhoneNumber = contacts.Contacto.FindById(contact.IdContacto).Telemovel;
        //            var dataCodingScheme = unicode ? DataCodingScheme.NoClass_16Bit : DataCodingScheme.NoClass_7Bit;
        //            var pdu = new SmsSubmitPdu(contact.MensagemRow.Descricao, destinationPhoneNumber, "", dataCodingScheme);                    
        //            OnPhoneSendingMessage("A enviar para o nº " + destinationPhoneNumber);
        //            _communication.SendMessage(pdu);
        //            state = MessageState.Enviada.ToString();
        //            error = "";
        //        }
        //        catch (Exception ex)
        //        {
        //            OnPhoneSendingMessage("");
        //            state = MessageState.NaoEnviada.ToString();
        //            error = ex.Message + " (" + ex.GetType() + ")";
        //        }
        //        long? groupId = null;
        //        if (!contact.IsIdGrupoNull()) groupId = contact.IdGrupo;
        //        Database.UpdateMessageContact(sqlTransaction, contact.IdMensagem, contact.IdContacto, groupId, state, error);
        //        var historicoDataSet = new HistoricoDataSet();
        //        var historic = historicoDataSet.Historico.NewHistoricoRow();
        //        historic.Id = -1;
        //        historic.Data = DateTime.Now;
        //        historic.Mensagem = contact.MensagemRow.Descricao;
        //        if(!contact.IsIdGrupoNull()) historic.GrupoContacto = groups.Grupo.FindById(contact.IdGrupo).Nome;
        //        historic.Contacto = contacts.Contacto.FindById(contact.IdContacto).Telemovel;
        //        historic.NomeContacto = contacts.Contacto.FindById(contact.IdContacto).Nome;
        //        historic.Estado = state;
        //        historic.Resultado = error;
        //        historicoDataSet.Historico.AddHistoricoRow(historic);
        //        Database.SaveHistoric(sqlTransaction, historic);
        //        return;
        //    }
        //    catch (Exception e)
        //    {
        //        ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível gravar o histórico da mensagem enviada.");
        //        return;
        //    }
        //}

        private void UpdateMessageState(long messageId)
        {
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var dataSet = Database.GetMessage(sqlTransaction, messageId);
                var atLeastOneSent = false;
                var atLeastOneNotSent = false;
                foreach(MensagemDataSet.ContactoRow contact in dataSet.Contacto)
                {
                    if(contact.Estado == MessageState.Enviada.ToString())
                        atLeastOneSent = true;
                    if (contact.Estado == MessageState.NaoEnviada.ToString() || contact.Estado == MessageState.AEnviar.ToString())
                        atLeastOneNotSent = true;
                }
                var state = MessageState.NaoEnviada.ToString();
                if(atLeastOneSent && !atLeastOneNotSent)
                    state = MessageState.Enviada.ToString();
                if (!atLeastOneSent && atLeastOneNotSent)
                    state = MessageState.NaoEnviada.ToString();
                if (atLeastOneSent && atLeastOneNotSent)
                    state = MessageState.ParcialmenteEnviada.ToString();
                Database.UpdateMessageState(sqlTransaction, messageId, state);
                sqlTransaction.Commit();
                OnMessageSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível actualizar o estado da mensagem.");
            } 
        }

        //private void SendSms(MensagemDataSet dataset)
        //{
        //    if (_communication == null)
        //        return;
        //    FixConnection();
        //    var sqlTransaction = _connection.BeginTransaction();
        //    try
        //    {
        //        var contacts = Database.GetContacts(sqlTransaction);
        //        var groups = Database.GetGroups(sqlTransaction);
        //        foreach (var contact in dataset.Contacto)
        //            SendContactSms(dataset.Mensagem[0].Unicode, groups, contacts, contact, sqlTransaction);
        //        sqlTransaction.Commit();
        //        OnPhoneSendingMessage("");
        //        OnHistoricSaved();                
        //    }
        //    catch (Exception e)
        //    {
        //        sqlTransaction.Rollback();
        //        ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível gravar a mensagem.");
        //    }
        //    UpdateMessageState(dataset.Mensagem[0].Id);
        //}

        public long[] GetMessageIdsToProcess()
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var result = Database.GetMessageIdsToProcess(sqlTransaction);
                sqlTransaction.Commit();
                return result;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível obter a lista de mensagens a processar.");
                return new long[0];
            }            
        }

        private bool ExistsOperator(string name)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var exists = Database.ExistsOperator(sqlTransaction, name);
                sqlTransaction.Commit();
                return exists;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível validar se existe a operadora.");
                return false;
            }
        }

        private long GetOperatorId(string name)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var exists = Database.GetOperatorId(sqlTransaction, name);
                sqlTransaction.Commit();
                return exists;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível ler o identificador da operadora.");
                return -1;
            }
        }

        private List<long> ImportedContactsFromFile(string fileName)
        {
            var contacts = new List<long>();
            var reader = new StreamReader(fileName, Encoding.UTF8, true);
            while (!reader.EndOfStream)
            {
                String line = reader.ReadLine();
                string[] values = line.Split(new[] { "\t" }, StringSplitOptions.None);
                if (values.Length == 0)
                    continue;
                var mobilePhone = values[0];
                var contactId = GetContactIdByMobilePhone(mobilePhone);
                if (contactId.HasValue)
                {
                    contacts.Add(contactId.Value);
                    continue;
                }
                var dataSet = new ContactoDataSet();
                var contact = dataSet.Contacto.NewContactoRow();
                contact.Id = -1;
                contact.Telemovel = values[0];
                contact.Nome = values[0];
                if (values.Length > 1 && values[1] != "") contact.Nome = values[1];
                if (values.Length > 2 && values[2] != "") contact.Telefone = values[2];
                if (values.Length > 3 && values[3] != "" && ExistsOperator(values[3])) contact.IdOperadora = GetOperatorId(values[3]);
                if (values.Length > 4 && values[4] != "") contact.Email = values[4];
                if (values.Length > 5 && values[5] != "") contact.Localidade = values[5];
                dataSet.Contacto.AddContactoRow(contact);
                SaveContact(dataSet);
                contacts.Add(dataSet.Contacto[0].Id);
            }
            return contacts;
        }

        private List<long> ImportedContactsFromQuery(long queryId)
        {
            var contacts = new List<long>();
            var queryDataSet = GetQuery(queryId);
            var contactDataSet = RunQueryComand(queryDataSet.Query[0].Comando);
            foreach (ContactoDataSet.ContactoRow contactRow in contactDataSet.Contacto)
            {
                if (contactRow.IsTelemovelNull())
                    continue;
                var contactId = GetContactIdByMobilePhone(contactRow.Telemovel);
                if (contactId.HasValue)
                {
                    contacts.Add(contactId.Value);
                    continue;
                }
                var dataSet = new ContactoDataSet();
                var contact = dataSet.Contacto.NewContactoRow();
                contact.Id = -1;
                if (!contactRow.IsNomeNull()) contact.Nome = contactRow.Nome;
                if (!contactRow.IsTelefoneNull()) contact.Telefone = contactRow.Telefone;
                if (!contactRow.IsTelemovelNull()) contact.Telemovel = contactRow.Telemovel;
                if (!contactRow.IsEmailNull()) contact.Email = contactRow.Email;
                if (!contactRow.IsLocalidadeNull()) contact.Localidade = contactRow.Localidade;
                if (!contactRow.IsIdOperadoraNull()) contact.IdOperadora = contactRow.IdOperadora;
                dataSet.Contacto.AddContactoRow(contact);
                SaveContact(dataSet);
                contacts.Add(dataSet.Contacto[0].Id);
            }
            return contacts;
        }

        private void AssociateContacsToGroup(long groupId, IEnumerable<long> contacts)
        {
            var dataSet = GetGroup(groupId);
            foreach (long contact in contacts)
                if (dataSet.Contacto.FindByIdGrupoIdContacto(groupId, contact) == null)
                    dataSet.Contacto.AddContactoRow(dataSet.Grupo[0], contact);
            SaveGroup(dataSet);
        }

        private void OnHistoricSaved()
        {
            EventHandler<EventArgs> saved = HistoricSaved;
            if (saved != null)
                saved(this, new EventArgs());
        }


        private Result SendContactSmsInternal(bool unicode, ContactoDataSet contacts, MensagemDataSet.ContactoRow contact)
        {
            var result = new Result();
            try
            {
                var destinationPhoneNumber = contacts.Contacto.FindById(contact.IdContacto).Telemovel;
                var dataCodingScheme = unicode ? DataCodingScheme.NoClass_16Bit : DataCodingScheme.NoClass_7Bit;
                var pdu = new SmsSubmitPdu(contact.MensagemRow.Descricao, destinationPhoneNumber, "", dataCodingScheme);
                OnPhoneSendingMessage("A enviar para o nº " + destinationPhoneNumber);
                _communication.SendMessage(pdu);
            }
            catch (Exception ex)
            {
                OnPhoneSendingMessage("");
                result.Add("", ex.Message + " (" + ex.GetType() + ")");
            }
            return result;
        }

        private void SendContactSms(bool unicode, GrupoDataSet groups, ContactoDataSet contacts, MensagemDataSet.ContactoRow contact)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var result = SendContactSmsInternal(unicode, contacts, contact);
                string state = result.Failed ? MessageState.NaoEnviada.ToString() : MessageState.Enviada.ToString();
                string error = result.Failed ? result.Errors[0].Message : "";
                long? groupId = null;
                if (!contact.IsIdGrupoNull()) groupId = contact.IdGrupo;
                Database.UpdateMessageContact(sqlTransaction, contact.IdMensagem, contact.IdContacto, groupId, state, error);
                var historicoDataSet = new HistoricoDataSet();
                var historic = historicoDataSet.Historico.NewHistoricoRow();
                historic.Id = -1;
                historic.Data = DateTime.Now;
                historic.Mensagem = contact.MensagemRow.Descricao;
                if (!contact.IsIdGrupoNull()) historic.GrupoContacto = groups.Grupo.FindById(contact.IdGrupo).Nome;
                historic.Contacto = contacts.Contacto.FindById(contact.IdContacto).Telemovel;
                historic.NomeContacto = contacts.Contacto.FindById(contact.IdContacto).Nome;
                historic.Estado = state;
                historic.Resultado = error;
                historicoDataSet.Historico.AddHistoricoRow(historic);
                Database.SaveHistoric(sqlTransaction, historic);
                sqlTransaction.Commit();                
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível gravar o histórico da mensagem enviada.");                
            }
        }

        private void SendSmsInternal(MensagemDataSet dataset)
        {
            if (_communication == null)
                return;
            var contacts = GetContacts();
            var groups = GetGroups();
            foreach (MensagemDataSet.ContactoRow contact in dataset.Contacto)
                if(contact.Estado == MessageState.AEnviar.ToString())
                    SendContactSms(dataset.Mensagem[0].Unicode, groups, contacts, contact);
            OnPhoneSendingMessage("");
            OnHistoricSaved();
            UpdateMessageState(dataset.Mensagem[0].Id);
        }



        #endregion

        public void SetLogFile()
        {
            try
            {
                LogFile = ConfigurationManager.AppSettings["LogFile"];
            }
            catch (Exception)
            {
                LogFile = "";
                ExceptionPublisher.ShowErrorMessage(OwnerWindow, "Não foi possível obter o ficheiro de logs.", ProductName);
            }
        }

        public void SetVersion()
        {
            try
            {
                Version = Convert.ToInt32(ConfigurationManager.AppSettings["Version"]);
            }
            catch (Exception)
            {
                Version = 1;                
            }
        }

        public bool Login(string userName, string password)
        {
            var dataSource = ConfigurationManager.AppSettings["FonteDados"];
            var databaseName = ConfigurationManager.AppSettings["NomeBaseDados"];
            var connectionString = "Data Source=" + dataSource + ";Initial Catalog=" + databaseName + ";User ID=" + userName + ";Password=" + password + ";MultipleActiveResultSets=True;";
            try
            {
                _connection = new SqlConnection(connectionString);
                _connection.Open();
            }
            catch (Exception e)
            {
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível ligar à base de dados.");
                return false;
            }
            return true;
        }

        public bool PhoneConnected()
        {
            if(!IsPhoneConnected(false) && !ShowPhoneConnection())
                return false;
            _communication = new GsmCommMain(ComPort, BaudRate, Timeout);
            _communication.PhoneConnected += CommunicationPhoneConnected;
            _communication.PhoneDisconnected += CommunicationPhoneDisconnected;
            try
            {
                _communication.Open();
            }
            catch (Exception ex)
            {
                MessageBox.Show(OwnerWindow, "Erro na conexão: " + ex.Message, "Instalação conexão", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return false;
            }
            //MessageBox.Show(OwnerWindow, "Ligação ao telemóvel efectuada com sucesso.", "Instalação conexão", MessageBoxButtons.OK, MessageBoxIcon.Information);
            PhoneIsConnected = true;
            OnPhoneConnectionChange();
            return true;
        }

        public void CloseConnections()
        {
            _connection.Close();
            try
            {
                if (_communication == null) return;
                _communication.PhoneConnected -= CommunicationPhoneConnected;
                _communication.PhoneDisconnected -= CommunicationPhoneDisconnected;
                if (_communication != null && _communication.IsOpen())
                    _communication.Close();
                _communication = null;
            }
            catch (Exception)
            {
                _communication = null;
            }
        }

        public OperadoraDataSet GetOperators()
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                OperadoraDataSet dataSet = Database.GetOperators(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível ler a lista de operadoras.");
                return new OperadoraDataSet();
            }
        }

        #region MessageType

        private void OnMessageTypeSaved()
        {
            EventHandler<EventArgs> saved = MessageTypeSaved;
            if (saved != null)
                saved(this, new EventArgs());
        }

        public event EventHandler<EventArgs> MessageTypeSaved;

        public TipoMensagemDataSet GetMessageTypes()
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                TipoMensagemDataSet dataSet = Database.GetMessageTypes(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível ler a lista de tipos de mensagem.");
                return new TipoMensagemDataSet();
            }
        }

        public TipoMensagemDataSet GetMessageType(long messageTypeId)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                TipoMensagemDataSet dataSet = Database.GetMessageType(sqlTransaction, messageTypeId);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível ler o tipo de mensagem.");
                return new TipoMensagemDataSet();
            }
        }

        public Result IsMessageTypeValid(TipoMensagemDataSet dataSet)
        {
            var result = new Result();
            var messageType = dataSet.TipoMensagem[0];
            if (string.IsNullOrEmpty(messageType.Nome)) result.Add("Nome", "Por favor, preencha o nome.");
            if (!string.IsNullOrEmpty(messageType.Nome) && messageType.Nome.Length > 50) result.Add("Descricao", "A descrição excede os 50 caracteres permitidos."); 
            if (!string.IsNullOrEmpty(messageType.Descricao) && messageType.Descricao.Length > 160) result.Add("Descricao", "A descrição excede os 160 caracteres permitidos."); 
            return result;
        }

        public void SaveMessageType(TipoMensagemDataSet messageType)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.SaveMessageType(sqlTransaction, messageType.TipoMensagem[0]);
                sqlTransaction.Commit();
                OnMessageTypeSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível gravar o tipo de mensagem.");
            }
        }

        public void DeleteMessageType(long id)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.DeleteMessageType(sqlTransaction, id);
                sqlTransaction.Commit();
                OnMessageTypeSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível eliminar o tipo de mensagem.");
            }
        }

        #endregion

        #region Contact

        private void OnContactSaved()
        {
            EventHandler<EventArgs> contactSaved = ContactSaved;
            if (contactSaved != null)
                contactSaved(this, new EventArgs());
        }

        private long? GetContactIdByMobilePhone(string mobilePhone)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var contactId = Database.GetContactIdByMobilePhone(sqlTransaction, mobilePhone);
                sqlTransaction.Commit();
                return contactId;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível ler o contacto dado o número de telemóvel.");
                return null;
            }
        }

        public event EventHandler<EventArgs> ContactSaved;        

        public ContactoDataSet GetContacts()
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                ContactoDataSet dataSet = Database.GetContacts(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível ler a lista de contactos.");
                return new ContactoDataSet();
            }
        }

        public ContactoDataSet GetContactsByGroupId(long groupId)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                ContactoDataSet dataSet = Database.GetContactsByGroupId(sqlTransaction, groupId);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível ler a lista de contactos de grupo actual.");
                return new ContactoDataSet();
            }
        }

        public ContactoDataSet GetContact(long contactId)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                ContactoDataSet dataSet = Database.GetContact(sqlTransaction, contactId);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível ler o contacto.");
                return new ContactoDataSet();
            }
        }

        public Result IsContactValid(ContactoDataSet dataSet)
        {
            var result = new Result();
            var contact = dataSet.Contacto[0];
            if (string.IsNullOrEmpty(contact.Nome)) result.Add("Nome", "Por favor, preencha o nome.");
            if (!string.IsNullOrEmpty(contact.Nome) && contact.Nome.Length > 150) result.Add("Nome", "O nome excede os 150 caracteres permitidos.");
            if (contact.IsTelemovelNull() || contact.Telemovel.Length == 0) result.Add("Telemovel", "Por favor, indique o numero de telemóvel.");
            if (!contact.IsTelemovelNull() && contact.Telemovel.Length > 50) result.Add("Telemovel", "O numero de telemóvel excede os 50 caracteres permitidos.");
            if (!contact.IsTelefoneNull() && contact.Telefone.Length > 50) result.Add("Telefone", "O numero de telefone excede os 50 caracteres permitidos.");
            if (!contact.IsEmailNull() && contact.Email.Length > 50) result.Add("Email", "O email excede os 50 caracteres permitidos.");
            if (!contact.IsLocalidadeNull() && contact.Localidade.Length > 150) result.Add("Localidade", "A locadidade excede os 150 caracteres permitidos.");            
            return result;
        }

        public void SaveContact(ContactoDataSet contact)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.SaveContact(sqlTransaction, contact.Contacto[0]);
                sqlTransaction.Commit();
                OnContactSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível gravar o contacto.");
            }
        }

        public void DeleteContact(long id)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.DeleteContact(sqlTransaction, id);
                sqlTransaction.Commit();
                OnContactSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível eliminar o contacto.");
            }
        }

        public bool ContactHasRelations(long id)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                bool result = Database.ContactHasRelations(sqlTransaction, id);
                sqlTransaction.Commit();
                return result;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível validar se o contacto tem dependências.");
                return true;
            }
        }

        #endregion

        #region Group

        private void OnGroupSaved()
        {
            EventHandler<EventArgs> groupSaved = GroupSaved;
            if (groupSaved != null)
                groupSaved(this, new EventArgs());
        }

        public event EventHandler<EventArgs> GroupSaved;

        public GrupoDataSet GetGroups()
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                GrupoDataSet dataSet = Database.GetGroups(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível ler a lista de grupos.");
                return new GrupoDataSet();
            }
        }

        public GrupoDataSet GetGroup(long groupId)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                GrupoDataSet dataSet = Database.GetGroup(sqlTransaction, groupId);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível ler o grupo.");
                return new GrupoDataSet();
            }
        }

        public Result IsGroupValid(GrupoDataSet dataset)
        {
            var result = new Result();
            var group = dataset.Grupo[0];
            if (string.IsNullOrEmpty(group.Nome)) result.Add("Nome", "Por favor, preencha o nome.");
            if (!string.IsNullOrEmpty(group.Nome) && group.Nome.Length > 50) result.Add("Nome", "O nome excede os 50 caracteres permitidos.");
            if (!group.IsDescricaoNull() && group.Descricao.Length > 150) result.Add("Descricao", "A descrição excede os 150 caracteres permitidos.");
            return result;
        }

        public void SaveGroup(GrupoDataSet dataset)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.SaveGroup(sqlTransaction, dataset.Grupo[0]);
                Database.UpdateGroupContacts(sqlTransaction, dataset.Contacto);
                sqlTransaction.Commit();
                OnGroupSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível gravar o grupo.");
            }
        }

        public void DeleteGroup(long id)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.DeleteGroup(sqlTransaction, id);
                sqlTransaction.Commit();
                OnGroupSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível eliminar o grupo.");
            }
        }

        public bool GroupHasRelations(long id)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                bool result = Database.GroupHasRelations(sqlTransaction, id);
                sqlTransaction.Commit();
                return result;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível validar se o grupo tem dependências.");
                return true;
            }
        }

        #endregion

        #region Message

        private void OnMessageSaved()
        {
            EventHandler<EventArgs> saved = MessageSaved;
            if (saved != null)
                saved(this, new EventArgs());
        }

        public event EventHandler<EventArgs> MessageSaved;

        public MensagemDataSet GetMessages()
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                MensagemDataSet dataSet = Database.GetMessages(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível ler a lista de mensagens.");
                return new MensagemDataSet();
            }
        }

        public MensagemDataSet GetMessage(long id)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                MensagemDataSet dataSet = Database.GetMessage(sqlTransaction, id);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível ler a mensagem.");
                return new MensagemDataSet();
            }
        }

        public Result IsMessageValid(MensagemDataSet dataset)
        {
            var result = new Result();
            var message = dataset.Mensagem[0];
            if (string.IsNullOrEmpty(message.Descricao)) result.Add("Descricao", "Por favor, preencha a descrição.");
            if (!string.IsNullOrEmpty(message.Descricao) && message.Unicode && message.Descricao.Length > 160) result.Add("Descricao", "A descrição excede os 160 caracteres permitidos.");
            if (!string.IsNullOrEmpty(message.Descricao) && !message.Unicode && message.Descricao.Length > 140) result.Add("Descricao", "A descrição excede os 140 caracteres permitidos.");
            if (message.IsDataNull()) result.Add("Data", "Por favor, preencha a data.");
            return result;
        }

        public void SaveMessage(MensagemDataSet dataset)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.SaveMessage(sqlTransaction, dataset.Mensagem[0]);
                Database.UpdateMessageContacts(sqlTransaction, dataset.Mensagem[0].Id, dataset.Contacto);
                sqlTransaction.Commit();
                OnMessageSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível gravar a mensagem.");
            }
            dataset.AcceptChanges();
            if(dataset.Mensagem[0].Data < DateTime.Now)
                SendSmsInternal(dataset);
        }

        public void DeleteMessage(long id)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.DeleteMessage(sqlTransaction, id);
                sqlTransaction.Commit();
                OnMessageSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível eliminar a mensagem.");
            }
        }

        #endregion     

        #region Query

        private void OnQuerySaved()
        {
            EventHandler<EventArgs> querySaved = QuerySaved;
            if (querySaved != null)
                querySaved(this, new EventArgs());
        }

        private static string GetValue(DataRow dataRow, string fieldName)
        {
            try
            {
                return dataRow.IsNull(fieldName) ? "" : dataRow[fieldName].ToString();
            }
            catch (Exception)
            {
                return "";
            }
        }

        private ContactoDataSet Contacts(DataTable dataTable)
        {
            var dataSet = new ContactoDataSet();
            long id = 0;
            foreach(DataRow dataRow in dataTable.Rows)
            {
                var contact = dataSet.Contacto.NewContactoRow();
                contact.Id = id--;
                if (GetValue(dataRow, "Nome") != "") contact.Nome = dataRow["Nome"].ToString();
                if (GetValue(dataRow, "Telemovel") != "") contact.Telemovel = dataRow["Telemovel"].ToString();
                if (GetValue(dataRow, "Telefone") != "") contact.Telefone = dataRow["Telefone"].ToString();
                string value = GetValue(dataRow, "Operador");
                if (value != "" && ExistsOperator(value)) contact.IdOperadora = GetOperatorId(value);
                if (GetValue(dataRow, "Email") != "") contact.Email = dataRow["Email"].ToString();
                if (GetValue(dataRow, "Localidade") != "") contact.Localidade = dataRow["Localidade"].ToString();
                dataSet.Contacto.AddContactoRow(contact);
            }
            return dataSet;
        }

        public event EventHandler<EventArgs> QuerySaved;

        public QueryDataSet GetQueries()
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                QueryDataSet dataSet = Database.GetQueries(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível ler a lista de queries.");
                return new QueryDataSet();
            }
        }

        public QueryDataSet GetQuery(long queryId)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                QueryDataSet dataSet = Database.GetQuery(sqlTransaction, queryId);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível ler a query.");
                return new QueryDataSet();
            }
        }

        public Result IsQueryValid(QueryDataSet dataSet)
        {
            var result = new Result();
            var contact = dataSet.Query[0];
            if (string.IsNullOrEmpty(contact.Nome)) result.Add("Nome", "Por favor, preencha o nome.");
            if (!string.IsNullOrEmpty(contact.Nome) && contact.Nome.Length > 150) result.Add("Nome", "O nome excede os 150 caracteres permitidos.");
            if (string.IsNullOrEmpty(contact.Comando)) result.Add("Comando", "Por favor, preencha o comando.");            
            return result;
        }

        public void SaveQuery(QueryDataSet contact)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.SaveQuery(sqlTransaction, contact.Query[0]);
                sqlTransaction.Commit();
                OnQuerySaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível gravar a query.");
            }
        }

        public Result IsQueryComandValid(string commandText)
        {
            FixConnection();
            var result = new Result();
            if (commandText.Length == 0)
            {
                result.Add("Error", "A propriedade CommandText não foi inicializada");
                return result;
            }
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.RunQueryCommand(sqlTransaction, commandText);
                sqlTransaction.Commit();
                result.Add("", "A query foi executada com sucesso.");
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                result.Add("Error", "Não foi possível executar a query." + Environment.NewLine + e.Message);                
            }
            return result;
        }        

        public ContactoDataSet RunQueryComand(string commandText)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var dataTable = Database.RunQueryCommand(sqlTransaction, commandText);
                sqlTransaction.Commit();
                return Contacts(dataTable);
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível executar a query.");
                return new ContactoDataSet();
            }            
        }

        public void DeleteQuery(long id)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.DeleteQuery(sqlTransaction, id);
                sqlTransaction.Commit();
                OnQuerySaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível eliminar a query.");
            }
        }

        #endregion

        public event EventHandler<EventArgs> HistoricSaved;

        public HistoricoDataSet GetHistorics()
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                HistoricoDataSet dataSet = Database.GetHistorics(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível ler a lista de histórico.");
                return new HistoricoDataSet();
            }
        }

        public void DeleteHistoric(DateTime dateTime)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.DeleteHistoric(sqlTransaction, dateTime);
                sqlTransaction.Commit();
                OnHistoricSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                ExceptionPublisher.PublishAndShowErrorMessage(OwnerWindow, e, LogFile, ProductName, "Não foi possível eliminar o histórico.");
            }
        }

        public void SendSms()
        {
            var messageIds = GetMessageIdsToProcess();
            foreach(long messageId in messageIds)
            {
                MensagemDataSet dataSet = GetMessage(messageId);
                SendSmsInternal(dataSet);
            }
        }

        public void SetupCommunicationSettings()
        {
            var communicationSettings = new CommunicationSettingsXtraForm
                                            {
                                                COMPort = ComPort,
                                                BaudRate = BaudRate,
                                                Timeout = Timeout
                                            };
            if(communicationSettings.ShowDialog() != DialogResult.OK)
                return;
            ComPort = communicationSettings.COMPort;
            BaudRate = communicationSettings.BaudRate;
            Timeout = communicationSettings.Timeout;            
        }

        public void TestCommunicationSettings()
        {
            var comm = new GsmCommMain(ComPort, BaudRate, Timeout);
            try
            {
                comm.Open();
                while (!comm.IsConnected())
                {
                    if (MessageBox.Show(OwnerWindow, "Nenhum telemóvel ligado.", "Instalação conexão", MessageBoxButtons.RetryCancel, MessageBoxIcon.Exclamation) == DialogResult.Cancel)
                    {
                        comm.Close();
                        return;
                    }
                }
                comm.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(OwnerWindow, "Erro na conexão: " + ex.Message, "Instalação conexão", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }
            MessageBox.Show(OwnerWindow, "Ligação ao telemóvel efectuada com sucesso.", "Instalação conexão", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
        
        public void ImportContacts()
        {
            var importContacts = new ImportContactsXtraForm {Version = Version, GroupsData = GetGroups(), QueriesData = GetQueries()};
            if(importContacts.ShowDialog() == DialogResult.Cancel)                       
                return;
            var groupId = importContacts.GroupId;
            if (importContacts.IsFile)
            {
                var contacts = ImportedContactsFromFile(importContacts.FileName);
                if (groupId.HasValue && contacts.Count > 0)
                    AssociateContacsToGroup(groupId.Value, contacts);
            }
            if(importContacts.IsQuery)
            {
                var contacts = ImportedContactsFromQuery(importContacts.Query);
                if (groupId.HasValue && contacts.Count > 0)
                    AssociateContacsToGroup(groupId.Value, contacts);
            }
        }

    }
}
